﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using RaytracerLib.MathLib;
using RaytracerLib.RenderableObjects;
using YAXLib;
using RaytracerLib.ShadowTesters;
using RaytracerLib.Rays;
using RaytracerLib.SpatialSubdivisioners;
using RaytracerLib.Materials;
using RaytracerLib.Lights;
using System.Linq;
using RaytracerLib.Cameras;
using RaytracerLib.Renderers;

namespace RaytracerLib
{
    public delegate void RenderEndEvent(bool a_all);
    public delegate void RenderStartEvent(RenderStartPhase a_phase);

    public class Scene
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Camera m_active_camera;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        internal Intersection NoIntersection { get; private set; }

        [YAXNode]
        public string Name = "";

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private RenderOptions m_render_options;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private MaterialsList m_materials;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private FilmsList m_films;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private RenderableObjectList m_renderable_objects;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private LightsList m_lights;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private CamerasList m_cameras;

        internal ShadowTester ShadowTester { get; private set; }
        internal SpatialSubdivisioner SpatialSubdivisioner { get; private set; }
        public Statistics Statistics { get; private set; }

        public RenderStartEvent RenderStart;
        public RenderEndEvent RenderEnd;

        public Scene()
        {
            NoIntersection = new Intersection() { Scene = this };

            RenderOptions = new RenderOptions();
            Statistics = new Statistics();
            RenderableObjects = new RenderableObjectList();
            Lights = new LightsList();
            Films = new FilmsList();
            Cameras = new CamerasList();
            Materials = new MaterialsList();

            #if DEBUG
            RenderStart += (phase) =>
            {
                if (phase == RenderStartPhase.ResetStatistics)
                    Intersection.IDCounter = 0;
            };
            #endif

            RenderStart += Statistics.RenderStart;
            RenderStart += (phase) => 
            {
                if (phase == RenderStartPhase.CreateShadowTester)
                    ShadowTester = ShadowTester.Create(this);
            };
            RenderStart += (phase) =>
            {
                if (phase == RenderStartPhase.CreateSpatialSubdivisioner)
                    SpatialSubdivisioner = new NonSpatialSubdivisioner(this);
            };

            RenderEnd += (all) => Statistics.RenderEnd();
            RenderEnd += (all) => GC.Collect();
        }

        [YAXOnDeserialized]
        private void OnDeserialized()
        {
            ActiveCamera = Cameras.FirstOrDefault(c => c.Active);
        }

        [YAXNode]
        public RenderOptions RenderOptions
        {
            get
            {
                return m_render_options;
            }
            private set
            {
                if (m_render_options != null)
                    RenderStart -= m_render_options.RenderStart;

                m_render_options = value;

                if (m_render_options != null)
                    RenderStart += m_render_options.RenderStart;
            }
        }

        [YAXCollection(ElementName = "Film")]
        public FilmsList Films
        {
            get
            {
                return m_films;
            }
            private set
            {
                m_films = value;
                m_films.Scene = this;
            }
        }

        [YAXCollection(ElementName = "Material")]
        public MaterialsList Materials
        {
            get
            {
                return m_materials;
            }
            private set
            {
                m_materials = value;
                m_materials.Scene = this;
            }
        }

        [YAXCollection(ElementName = "Light")]
        public LightsList Lights
        {
            get
            {
                return m_lights;
            }
            private set
            {
                m_lights = value;
                m_lights.Scene = this;
            }
        }

        [YAXCollection(ElementName = "Camera")]
        public CamerasList Cameras
        {
            get
            {
                return m_cameras;
            }
            private set
            {
                m_cameras = value;
                m_cameras.Scene = this;
            }
        }

        [YAXCollection(ElementName = "RenderableObject")]
        public RenderableObjectList RenderableObjects
        {
            get
            {
                return m_renderable_objects;
            }
            private set
            {
                m_renderable_objects = value;
                m_renderable_objects.Scene = this;
            }
        }

        public Camera ActiveCamera
        {
            get
            {
                return m_active_camera;
            }
            set
            {
                if (m_active_camera == value)
                    return;

                if (m_active_camera != null)
                    m_active_camera.Active = false;

                m_active_camera = value;

                if (m_active_camera != null)
                {
                    if (!m_cameras.Contains(m_active_camera))
                        m_cameras.Add(m_active_camera);

                    m_active_camera.Active = true;
                }
            }
        }

        public IEnumerable<SceneActor> Actors
        {
            get
            {
                foreach (var light in Lights)
                    yield return light;
                foreach (var obj in RenderableObjects)
                    yield return obj;
                foreach (var obj in Cameras)
                    yield return obj;
            }
        }

        public void SaveToFile(string a_file_name)
        {
            Materials.RemoveNotUsed();

            YAXSerializer.SaveToFile(a_file_name, this);
        }

        public static Scene LoadFromFile(string a_file_name)
        {
            return YAXSerializer.LoadFromFile<Scene>(a_file_name);
        }

        public Scene DeepClone()
        {
            return YAXSerializer.DeepClone(this);
        }

        public AABB GetBoundBox()
        {
            AABB aabb = Actors.First().BoundBox;

            foreach (var el in Actors.Skip(1))
            {
                if (el.BoundBox.IsFinite)
                    aabb = AABB.Merge(aabb, el.BoundBox);
            }

            return aabb;
        }

        public void ScaleAbsolute(double a_scale)
        {
            foreach (var el in Actors)
                el.ScaleAbsolute(a_scale);
            RenderOptions.LightSpeed = RenderOptions.LightSpeed * a_scale;
        }

        public void Normalize()
        {
            AABB aabb = GetBoundBox();
            double scale = 1 / (aabb.Minimum - aabb.Maximum).Abs().Max();
            ScaleAbsolute(scale);
        }

        public Renderers.Renderer CreateRenderer()
        {
            Renderer renderer = YAXSerializer.DeepClone(RenderOptions.Renderer);
            renderer.Scene = this;
            return renderer;
        }
    }
}